module.exports = {


  friendlyName: 'Download vulnerabilities csv',


  description: 'Download vulnerabilities csv file (returning a stream).',


  inputs: {

    minSeverity: {
      description: 'Optional filter to only get vulnerabilities whose `severity` is >= the specified value.',
      type: 'number',
      defaultsTo: 0,
    },

    maxSeverity: {
      description: 'Optional filter to only get vulnerabilities whose `severity` is <= the specified value.',
      type: 'number',
      defaultsTo: 10,
    },

    sortBy: {
      description: 'An optional facet to sort vulnerabilities by.',
      type: 'string',
      isIn: [
        'cveId',
        'severity',
        'hasKnownExploit',
        'publishedAt',
        'resolvedAt',
      ],
      defaultsTo: 'publishedAt'
    },

    sortDirection: {
      type: 'string',
      isIn: [
        'ASC',
        'DESC',
      ],
      defaultsTo: 'DESC'
    },

    page: {
      description: 'The zero-indexed page number.',
      type: 'number',
      defaultsTo: 0
    },

    teamApid: {
      description: 'The ID of the Team to filter by, or 0 to only include hosts with no team, or undefined to not filter by any team.',
      type: 'number',
    },

    pageSize: {
      description: 'The number of vulnerabilities to export',
      type: 'number',
      defaultsTo: 9999,
    },

    exportType: {
      description: 'The type of CSV export this will be.',
      type: 'string',
      isIn: [
        'resolvedAndVulnerableInstalls',
        'overview'
      ],
    },

  },


  exits: {
    success: {
      outputFriendlyName: 'File',
      outputDescription: 'The streaming bytes of the file.',
      outputType: 'ref'
    },
    emptyExport: {
      description: 'No matching vulnerabilities could be found with the provided filters',
      responseType: 'notFound',
    }
  },


  fn: async function ({minSeverity, maxSeverity, sortBy, sortDirection, page, pageSize, teamApid, exportType}) {

    // Generate a random room name.
    let roomId = await sails.helpers.strings.random();
    if(this.req.isSocket) {
      // Add the requesting socket to the room.
      sails.sockets.join(this.req, roomId);
    }

    let includeResolvedInstalls = false;
    // If the export type is resolvedAndVulnerableInstalls, we'll ask the get-vulnerabilities helper to include resolved install information in the affectedInstalls array.
    if(exportType === 'resolvedAndVulnerableInstalls'){
      includeResolvedInstalls = true;
    }

    // Get a report from the vulnerability helper
    let report = await sails.helpers.getVulnerabilities.with({minSeverity, maxSeverity, sortBy, sortDirection, page, pageSize, teamApid, includeResolvedInstalls})
    .intercept('noMatchingVulnerabilities', ()=>{
      return 'emptyExport';
    });
    let stream = require('stream');
    let csvString = '';
    // Create a writeable stream we'll use to create the csvString.
    let writableStream = new stream.Writable({//[?]: https://nodejs.org/api/stream.html#writable_writechunk-encoding-callback
      write(chunk, encoding, callback) {
        csvString += chunk.toString();
        callback();
      }
    });

    let csv = require('fast-csv');
    let generatingCsv = csv.format({headers: true});
    // Pass the writableStream into generatingCsv.
    generatingCsv.pipe(writableStream);// [?]: https://c2fo.github.io/fast-csv/docs/formatting/methods#write

    // Now build the CSV report.
    for (let vulnerability of report.entries) {
      // If the export type is overview, we'll add a row for every CVE in the report.
      if (exportType === 'overview') {
        let csvRowForThisVulnerability = {};
        csvRowForThisVulnerability['CVE ID'] = vulnerability.cveId;
        csvRowForThisVulnerability['Severity'] = vulnerability.severity;
        csvRowForThisVulnerability['Has known exploit'] = !!vulnerability.hasKnownExploit;
        csvRowForThisVulnerability['CVE description'] = vulnerability.cveDescription ? vulnerability.cveDescription : 'N/A';
        csvRowForThisVulnerability['Publish date'] = new Date(vulnerability.publishedAt);
        csvRowForThisVulnerability['Resolved Date'] = vulnerability.resolvedAt !== 0 ? new Date(vulnerability.resolvedAt) : 'N/A';
        csvRowForThisVulnerability['Number of affected hosts'] = vulnerability.numAffectedHosts;
        generatingCsv.write(csvRowForThisVulnerability);
      } else if (exportType === 'resolvedAndVulnerableInstalls') {
        // If the export type is resolvedAndVulnerableInstalls, we'll add a row for every software install included the report.
        // To do this, we'll iterate through the hosts affected by this vulnerability, and add a row for each vulnerable install record that is affected by this CVE on this host.
        for (let host of vulnerability.affectedHosts) {
          let installsForThisHost = vulnerability.affectedInstalls.filter((install) => {
            return install.affectedHost === host.id;
          });
          for (let install of installsForThisHost) {
            let csvRowForThisInstall = {};
            csvRowForThisInstall['CVE ID'] = vulnerability.cveId;
            csvRowForThisInstall['Severity'] = vulnerability.severity;
            csvRowForThisInstall['Has known exploit'] = !!vulnerability.hasKnownExploit;
            csvRowForThisInstall['CVE description'] = vulnerability.cveDescription ? vulnerability.cveDescription : 'N/A';
            csvRowForThisInstall['Publish date'] = new Date(vulnerability.publishedAt);
            csvRowForThisInstall['Resolved Date'] = install.uninstalledAt !== 0 ? new Date(install.uninstalledAt) : 'N/A';
            csvRowForThisInstall['Affected software name'] = install.name;
            csvRowForThisInstall['Affected software version'] = install.version;
            csvRowForThisInstall['Resolved in version'] = install.resolvedInVersion ? install.resolvedInVersion : 'N/A';
            csvRowForThisInstall['Affected software URL'] = install.url;
            csvRowForThisInstall['Vulnerable software detected on'] = new Date(install.installedAt);
            csvRowForThisInstall['Host Fleet URL'] = sails.config.custom.fleetBaseUrl + '/hosts/' + encodeURIComponent(host.fleetApid);
            csvRowForThisInstall['Host display name'] = host.displayName;
            csvRowForThisInstall['Host team'] = host.teamDisplayName;
            csvRowForThisInstall['Host serial number'] = host.hardwareSerialNumber;
            csvRowForThisInstall['Host UUID'] = host.uuid;
            csvRowForThisInstall['Host team ID'] = host.teamApid !== 0 ? host.teamApid : 'N/A';
            generatingCsv.write(csvRowForThisInstall);
          }//∞
        }//∞
      }
    }//∞
    generatingCsv.end();
    // After the the csvString has been generated by the writableStream, broadcast the csvString to the requesting user's socket.
    writableStream.on('finish', () => {
      if(this.req.isSocket){
        // Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
        sails.sockets.broadcast(roomId, 'csvExportDone', csvString);
        // Unsubscribe the socket from the room.
        sails.sockets.leave(this.req, roomId);
      } else {
        return csvString;
      }
    });
  }


};
